package scala.meta
package internal
package trees

import org.scalameta.internal.MacroHelpers

import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context

// Generates quasiquote definition boilerplate for a given interpolator
// and then injects it into the annottee.
// See usage examples in the `quasiquotes` project.
class quasiquote[T](qname: String) extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro QuasiquoteMacros.impl
}

class QuasiquoteMacros(val c: Context) extends MacroHelpers {
  import c.universe._
  val ReificationMacros = q"_root_.scala.meta.internal.quasiquotes.ReificationMacros"
  def impl(annottees: c.Tree*): c.Tree = annottees.transformAnnottees {
    new ImplTransformer {
      val q"new $_[..$qtypes](${qname: String}).macroTransform(..$_)" = c.macroApplication
      override def transformClass(cdef: ClassDef, mdef: ModuleDef): List[ImplDef] = {
        val q"$mods class $name[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =
          cdef
        val q"$mmods object $mname extends { ..$mearlydefns } with ..$mparents { $mself => ..$mstats }" =
          mdef
        val mmods1 = Modifiers(mmods.flags, TypeName("meta"), mmods.annotations)
        if (stats.nonEmpty) c.abort(cdef.pos, "@quasiquote classes must have empty bodies")
        val qmodule = {
          val qtypesLub = lub(qtypes.map(qtype =>
            try c.typecheck(qtype.duplicate, c.TYPEmode).tpe
            catch { case c.TypecheckException(pos, msg) => c.abort(pos.asInstanceOf[c.Position], msg) }
          ))
          q"""
            object ${TermName(qname)} {
              import scala.language.experimental.macros
              def apply[T >: Any](args: T*)(implicit dialect: _root_.scala.meta.Dialect): $qtypesLub = macro $ReificationMacros.apply
              def unapply(scrutinee: Any)(implicit dialect: _root_.scala.meta.Dialect): Any = macro $ReificationMacros.unapply
            }
          """
        }
        val qparser = {
          val qmonadicResult = qtypes.map(qtype =>
            q"""
              _root_.scala.Predef.implicitly[Parse[$qtype]].apply(input, dialect)
            """
          ).reduce((acc, curr) =>
            q"""
              ($acc) match {
                case _: Parsed.Error => $curr
                case x => x
              }
            """
          )
          q"""
            private[meta] def parse(input: _root_.scala.meta.inputs.Input, dialect: _root_.scala.meta.Dialect) = {
              import _root_.scala.meta.parsers._
              ($qmonadicResult) match {
                case x: Parsed.Error => throw x.details
                case x: Parsed.Success[_] => x.tree
              }
            }
          """
        }
        val stats1 = stats :+ qmodule
        val mstats1 = mstats :+ qparser
        val cdef1 =
          q"$mods class $name[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats1 }"
        val mdef1 =
          q"$mmods1 object $mname extends { ..$mearlydefns } with ..$mparents { $mself => ..$mstats1 }"
        List(cdef1, mdef1)
      }
    }
  }
}
